fix(miner): reclaim worktree slots by lease age, not cross-container PID liveness#7131
Conversation
…PID liveness Fleet mode runs separate containers over one shared worktree-allocator store, each with its own PID namespace, so reclaimOrphanedAllocations' isProcessAlive(owner_pid) check is meaningless across containers: a dead pid can collide with a live one in another namespace (a genuinely orphaned slot is never reclaimed) and a live pid looks dead the moment another container opens the store (a running attempt's slot is stolen mid-write). Reclaim active slots by allocated_at age past a configurable maxLeaseMs (default 6h), mirroring portfolio-queue-expiry.js's DEFAULT_MAX_LEASE_MS/ sweepStuckItems and claim-ledger's age-based sweep. Record an owner_host qualifier next to owner_pid and only trust the isProcessAlive fast path when the slot was leased on this host, so a crashed local owner still frees its slot immediately while a foreign pid verdict is ignored. reclaimOrphanedAllocations takes an injectable nowMs and maxLeaseMs; a pre-existing store is migrated in place with ALTER TABLE ADD COLUMN. Closes JSONbored#7085
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7131 +/- ##
=======================================
Coverage 93.75% 93.75%
=======================================
Files 692 692
Lines 68830 68851 +21
Branches 18781 18781
=======================================
+ Hits 64533 64554 +21
Misses 3302 3302
Partials 995 995
Flags with carried forward coverage won't be shown. Click here to find out more.
|
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-17 23:34:50 UTC
Review summary Nits — 5 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
Summary
worktree-allocator.js's orphan reclaim no longer relies solely onisProcessAlive(owner_pid), which is meaningless across fleet mode's separate containers (each has its own PID namespace over one shared data volume). It now converges on the same age-based convention every sibling shared-lease store already uses (portfolio-queue-expiry.js'sDEFAULT_MAX_LEASE_MS/sweepStuckItems,claim-ledger'sDEFAULT_MAX_CLAIM_AGE_MS).owner_hostqualifier alongsideowner_pid(the gap the issue calls out: the pid was stored "with no host/container qualifier"). The PID-liveness check is retained only as a same-host fast path — it is trusted just whenowner_hostmatches this process's host — so a crashed local owner still frees its slot immediately, while a foreign pid verdict is never trusted.reclaimOrphanedAllocationsnow takes an injectablenowMsand a configurablemaxLeaseMs(default 6h, well above the 30-minute floor since a worktree lease spans a whole coding attempt), mirroringsweepStuckItems's signature and the existingmaxConcurrencyoption pattern.PRAGMA table_info+ALTER TABLE ADD COLUMN owner_host, the same technique asattempt-log.js'sensureOutcomeColumns); a migrated row keepsowner_hostNULL and is governed by the age fallback until re-acquired.This closes both documented failure modes: a genuinely orphaned slot whose dead pid collides with a live pid in another container is now reclaimed by age; a live worker's slot is no longer stolen the moment another container opens the shared store, because a recent lease is protected regardless of the pid verdict.
Closes #7085
Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #7085).Validation
git diff --checknpm run typechecknpm run test:coveragelocally — 100% of the changed lines and branches inworktree-allocator.jsare covered (both age-threshold branches crossed with bothisProcessAliveoutcomes, bothowner_hostpresence/match branches, theowner_pidnull/dead/alive branches, the unparseable-allocated_atpath, theALTER TABLEmigration path, and the invalid-config guards).npm run build:miner,npm run test:miner-pack,npm run test:miner-deployment-docs-audit,npm run miner:env-reference:checkIf any required check was skipped, explain why:
ui:*,test:workers,build:mcp, andui:openapi:checkare not applicable. No env-var reads were added/removed, so no generated reference changes.Safety
Notes
portfolio-queue-expiry.js's 30-minute floor because a single worktree lease covers clone + agent run + push, which can legitimately run for hours; the same-hostisProcessAlivefast path still frees a crashed local owner immediately, so the age fallback only ever governs the cross-container case.